#define _GNU_SOURCE

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<stdbool.h>
#include<dirent.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<pwd.h>
#include<grp.h>
#include<time.h>


//Ubuntu上的stat.h中提供的宏定义为ISLNK
#ifndef S_ISLINK
#define S_ISLINK S_ISLNK
#endif

#define USER_MODE(st, code) (code&st)==code

char* get_mode_des(mode_t st_mode){
	char* des = (char*)malloc(sizeof(char)*11);

	for(int i=0;i<10;i++)
		des[i]='-';
	des[10]='\0';

	//解析类型域文件类型
	if(S_ISREG(st_mode)) des[0] = '-';
	else if(S_ISDIR(st_mode)) 	des[0] = 'd';
	else if(S_ISCHR(st_mode)) 	des[0] = 'c';
	else if(S_ISBLK(st_mode)) 	des[0] = 'b';
	else if(S_ISFIFO(st_mode)) 	des[0] = 'p';
	else if(S_ISSOCK(st_mode))	des[0] = 's';
	else if(S_ISLINK(st_mode)) 	des[0] = 'l';
	else des[0] = 'u';
	//解析所有者权限
	if(USER_MODE(st_mode, S_IRWXU)) 	{ des[1]='r'; des[2]='w'; des[3]='x';}
	else {
		if(USER_MODE(st_mode, S_IRUSR))	{ des[1]='r';}
		if(USER_MODE(st_mode, S_IWUSR)) { des[2]='w';}
		if(USER_MODE(st_mode, S_IXUSR)) { des[3]='x';}
	}
	//解析用户组权限
	if(USER_MODE(st_mode, S_IRWXG))		{ des[4]='r'; des[5]='w'; des[6]='x';}
	else{
		if(USER_MODE(st_mode, S_IRGRP))	{ des[4]='r';}
		if(USER_MODE(st_mode, S_IWGRP))	{ des[5]='w';}
		if(USER_MODE(st_mode, S_IXGRP))	{ des[6]='x';}
	}
	//解析其他用户权限
	if(USER_MODE(st_mode, S_IRWXO))		{ des[7]='r'; des[8]='w'; des[9]='x';}
	else{
		if(USER_MODE(st_mode, S_IROTH))	{ des[7]='r';}
		if(USER_MODE(st_mode, S_IWOTH))	{ des[8]='w';}
		if(USER_MODE(st_mode, S_IXOTH))	{ des[9]='x';}
	}

	return des;
}

char* get_user_name(uid_t uid){
	struct passwd *user_info = getpwuid(uid);
	if(user_info==NULL) 
		return NULL;

	return user_info->pw_name;
}

char* get_group_name(gid_t gid){
	struct group *group_info = getgrgid(gid);
	if(group_info==NULL)
		return NULL;

	return group_info->gr_name;
}

char* get_time(){
	time_t rawtime;
	struct tm *time_info;
	char* time_str = (char*)malloc(sizeof(char)*16);

	time(&rawtime);
	time_info = localtime(&rawtime);

	sprintf(time_str, "%04d-%02d-%02d %02d:%02d", time_info->tm_year+1900, time_info->tm_mon, time_info->tm_hour, time_info->tm_min, time_info->tm_sec);

	return time_str;
}

char* get_file_info(char* file_name, struct stat* file_stat){
	char* info = (char*)malloc(sizeof(char)*512);

	char* mode_des = get_mode_des(file_stat->st_mode);
	size_t nlink = (size_t)  file_stat->st_nlink;
	char* user_name = get_user_name(file_stat->st_uid);
	char* group_name = get_group_name(file_stat->st_gid);
	size_t file_size = (size_t) file_stat->st_size;
	char* time = get_time();

	if(user_name==NULL || group_name==NULL){
		return NULL;
	}

	sprintf(info, "%s %zu %s %s %zu %s %s", mode_des, nlink, user_name, group_name, file_size, time, file_name);
	return info;
}

int get_file_list(DIR *dir, char** file_list, bool show_self, bool show_parent, bool show_hide){
	struct dirent* file;
	char *name;
	int i=0;
	while((file = readdir(dir)) != NULL){
		name = file->d_name;
		//跳过自己
		if(!show_self && strcmp(name,".")==0)
			continue;
		//跳过父文件夹
		if(!show_parent && strcmp(name,"..")==0)
			continue;
		//跳过隐藏文件
		if(!show_hide && file->d_reclen>1 && name[0] == '.' && strcmp(name,"..")!=0 && strcmp(name,".")!=0)
			continue;
		file_list[i++] = file->d_name;
	}
	return i;
}

struct stat* get_file_stat(const char *path, bool link_flag){
	//link_flag 为true，返回符号链接文件属性
	//link_flag为false，返回引用文件属性

 	struct stat *buf = (struct stat*)malloc(sizeof(struct stat));
 	int code;
 	if(link_flag) code = lstat(path, buf);
 	else code = stat(path,buf);

 	//code 不为0则错误
 	if(code!=0) return NULL;
 	return buf;
}

void print_usage(){
	char *usage =	"Usage: exe3 [option]...\n"
					"	List information about the files in the current directory. Similar to ls -l.\n"
					"	-a, --all\t\tshow all the files includes the hidden ones,except directory itself and parent directory.\n"
					"	-s, --self\t\tshow information of the directory itself.\n"
					"	-p, --parent\t\tshow information of the parent directory.\n"
					"	-l, --link\t\tshow information of the link symbol file itself instead of the linked one.\n"
					"	-h, --help\t\tshow usage.\n";
	printf("%s",usage);
}


int main(int argc,char** argv){
	bool show_self = false;
	bool show_parent = false;
	bool show_hide = false;
	bool link_flag = false;

	if(argc>1){
		for(int i=1;i<argc;i++){
			if(strcmp(argv[i],"-a")==0||strcmp(argv[i],"--all")==0) 		{ show_hide = true; }
			else if(strcmp(argv[i],"-s")==0||strcmp(argv[i],"--self")==0) 	{ show_self = true; }
			else if(strcmp(argv[i],"-p")==0||strcmp(argv[i],"--parent")==0)	{ show_parent = true; }
			else if(strcmp(argv[i],"-l")==0||strcmp(argv[i],"--link")==0)	{ link_flag = true; }
			else if(strcmp(argv[i],"-h")==0||strcmp(argv[i],"--help")==0)	{ print_usage(); return 0; }
			else{
				fprintf(stderr, "unrecognized option \'%s\'\nTry \'exe3 -h for usage\n", argv[i]);
					return -1;
			}
		}
	}

	//获取并打开当前目录
	DIR* dir = opendir(get_current_dir_name());
	if(dir==NULL){
		fprintf(stderr, "open directory fail\n");
		return -1;
	}


	//读取目录文件列表
	int file_list_size;
	char* file_list[1024];

	file_list_size = get_file_list(dir, file_list, show_self, show_parent, show_hide);


	struct stat *file_stat;
	char* file_info;
	for(int i=0;i<file_list_size;i++){
		file_stat = get_file_stat(file_list[i], link_flag);
		if(file_stat==NULL){
			printf("get file stat error for %s\n", file_list[i]);
			continue;
		}
		file_info = get_file_info(file_list[i], file_stat);
		if(file_info==NULL){
			printf("get file info error for %s\n", file_list[i]);
			continue;
		}
		printf("%s\n", file_info);
		free(file_stat);
		free(file_info);
	}

	if(closedir(dir)==-1){
		printf("close dir fail\n");
		exit(-1);
	}

	return 0;
}